refactor: replace lowlevel Server decorators with on_* constructor kwargs#1985
refactor: replace lowlevel Server decorators with on_* constructor kwargs#1985
Conversation
acea3d7 to
8e1d947
Compare
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
7221cca to
7e9e53f
Compare
…args Replace the decorator-based handler registration on the lowlevel Server with direct on_* keyword arguments on the constructor. Handlers are raw callables with a uniform (ctx, params) -> result signature. - Server constructor takes on_list_tools, on_call_tool, etc. - String-keyed dispatch instead of type-keyed - Remove RequestT generic from Server (transport-specific, not bound at construction) - Delete handler.py and func_inspection.py (no longer needed) - Update ExperimentalHandlers to use callback-based registration - Update MCPServer to pass on_* kwargs via _create_handler_kwargs() - Update migration docs and docstrings
- Fix all migration.md examples to use ServerRequestContext instead of RequestContext - Fix all imports to use 'from mcp.server import Server, ServerRequestContext' - Apply ruff formatting to code examples - Add Server constructor calls to examples that were missing them - Re-export ServerRequestContext from mcp.server.__init__ - Add type hints to TaskResultHandler docstring example
…erver Move handler functions from a dict-returning method to private methods on MCPServer, passed directly to the Server constructor by name. This eliminates the **kwargs unpacking pattern and makes the handler registration explicit.
Allow users to override default task handlers by passing on_get_task, on_task_result, on_list_tasks, and on_cancel_task to enable_tasks(). User-provided handlers are registered first; defaults fill in the rest.
The subscribe capability in ResourcesCapability was hardcoded to False, even when on_subscribe_resource handler was provided. Now dynamically checks whether a subscribe handler is registered. Also applies ruff formatting to experimental.py.
e9df629 to
ca8fd2e
Compare
Migrate test files from the old decorator-based handler registration to the new on_* constructor kwargs pattern. Key changes: - Replace @server.list_tools(), @server.call_tool(), etc. decorators with on_list_tools, on_call_tool, etc. kwargs on Server() - Replace server.request_context access with ctx parameter (first argument to all handlers) - Handlers now receive (ctx, params) and return full result types (e.g. ListToolsResult instead of list[Tool]) - Convert experimental task decorators to enable_tasks() kwargs - Add LifespanContextT default to ServerRequestContext - Widen on_call_tool return type to include CreateTaskResult - Delete redundant tests/shared/test_memory.py - Simplify tests to use Client where possible
Convert the 3 remaining experimental task server test files: - test_integration.py: proper lifespan + Client pattern - test_run_task_flow.py: constructor kwargs + Client pattern - test_server.py: enable_tasks() kwargs + Client pattern All tests use proper typing with ServerRequestContext and constructor kwargs instead of decorators.
- Delete test_func_inspection.py (tested removed create_call_wrapper) - Delete test_lowlevel_input_validation.py and test_lowlevel_output_validation.py (tested jsonschema validation removed from low-level server) - Migrate test_read_resource.py to E2E with Client - Migrate test_129_resource_templates.py to E2E with Client - Migrate test_output_schema_validation.py to constructor kwargs (removed bypass_server_output_validation hack, no longer needed) - Migrate test_ws.py, test_sse.py, test_streamable_http.py from Server subclasses with decorators to standalone handlers with constructor kwargs - Replace self.request_context contextvar with ctx parameter - Replace global _server_lock with typed ServerState lifespan context - Fix import paths for TextContent, ClientRegistrationOptions, RevocationOptions, StreamableHTTPServerTransport
enable_tasks registers default handlers for all task methods, so partial capabilities aren't currently possible. Skipped tests with TODO(maxisbey) to revisit when low-level API supports selectively enabling/disabling individual task capabilities.
…attern - Convert all low-level Server examples from decorator-based handlers to constructor on_* kwargs pattern - Replace server.request_context with ctx parameter passed to handlers - Replace auto-wrapped return values (dict, str, bytes) with explicit result type construction (CallToolResult, ReadResourceResult, etc.) - Update everything-server to use _add_request_handler for subscribe/ unsubscribe/logging handlers (MCPServer doesn't expose these yet) - Update migration docs with detailed section on removed auto-wrapping - Update README snippets
Remove references to automatic content/structured conversion and the four return type options that no longer exist. Low-level handlers now always return CallToolResult directly.
- Remove unused _add_notification_handler from Server - Add pragma no cover for dead dict code path in MCPServer._handle_call_tool - Add E2E test for MCPServer.completion() decorator - Replace unused handle_list_tools stubs with raise NotImplementedError - Add pragma no cover for skipped tests in test_server and test_spec_compliance - Flatten nested completion handler logic to eliminate untaken branches - Fix test_88_random_error to use assert + shared return path - Clean up unused imports (Tool, ToolExecution, TASK_REQUIRED)
|
Although CI is all green, I still want to manually test all the examples that were modified. |
Kludex
left a comment
There was a problem hiding this comment.
Please check if copilot's comments make sense. I don't think it's reasonable for me to review at this point - but if there's anything specific that you want me to look at, happy to.
There was a problem hiding this comment.
Pull request overview
This PR completes the refactor of the low-level Server API by removing decorator-based handler registration and moving to constructor on_* keyword arguments with uniform (ctx, params) -> result handler signatures. It updates the test suite, examples, and supporting server/context utilities to align with the new handler model and method-string dispatch.
Changes:
- Migrates tests and examples from
@server.*()decorators toServer(..., on_*=...)handlers returning concrete MCP result models. - Updates server/context plumbing to support the new request context flow (context passed into handlers; request context fields now optional for notifications).
- Refreshes documentation/migration guidance to describe the new low-level
ServerAPI and handler contracts.
Reviewed changes
Copilot reviewed 71 out of 71 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/shared/test_ws.py | Updates WS test server to use on_* constructor handlers and typed params/results. |
| tests/shared/test_sse.py | Updates SSE test server(s) to on_* handlers; shifts request-context usage to ctx. |
| tests/shared/test_session.py | Updates cancellation test to new ServerRequestContext + CallToolRequestParams signature. |
| tests/shared/test_progress_notifications.py | Updates progress notification handlers and tool calls to new meta/params model. |
| tests/shared/test_memory.py | Removes legacy memory-stream example test that depended on decorator API. |
| tests/server/test_streamable_http_manager.py | Updates streamable-http tests to new low-level Server imports and on_list_tools. |
| tests/server/test_session.py | Updates capability tests to rely on on_* presence instead of decorators. |
| tests/server/test_read_resource.py | Reworks read-resource tests to use Client(server) and explicit ReadResourceResult contents. |
| tests/server/test_lowlevel_tool_annotations.py | Simplifies tool-annotation test using Client(server) and on_list_tools. |
| tests/server/test_lowlevel_output_validation.py | Removes legacy low-level output validation tests (old decorator/validation behavior). |
| tests/server/test_lowlevel_input_validation.py | Removes legacy low-level input validation tests (old decorator/validation behavior). |
| tests/server/test_lifespan.py | Updates lifespan/tool handler to new (ctx, params) signature and result type. |
| tests/server/test_completion_with_context.py | Updates completion tests to use on_completion and CompleteRequestParams/CompleteResult. |
| tests/server/test_cancel_handling.py | Updates cancel-handling test to use on_list_tools/on_call_tool and CallToolResult. |
| tests/server/mcpserver/test_server.py | Adds/updates MCPServer completion decorator test coverage. |
| tests/server/mcpserver/prompts/test_manager.py | Fixes imports to use TextContent from mcp.types. |
| tests/server/mcpserver/prompts/test_base.py | Fixes imports to use TextContent from mcp.types. |
| tests/server/mcpserver/auth/test_auth_integration.py | Updates auth settings imports split between routes/settings modules. |
| tests/server/lowlevel/test_server_pagination.py | Updates pagination tests to Client(server) flow and (ctx, params) handlers. |
| tests/server/lowlevel/test_server_listing.py | Updates list tests to Client(server) and explicit result models. |
| tests/server/lowlevel/test_func_inspection.py | Removes tests for old signature-inspection wrapper (decorator era). |
| tests/issues/test_88_random_error.py | Updates reproduction test to new low-level server handler signatures/results. |
| tests/issues/test_342_base64_encoding.py | Updates base64 regression test to use MCPServer + Client read_resource path. |
| tests/issues/test_1574_resource_uri_validation.py | Updates URI roundtrip tests to new low-level handler signatures/results. |
| tests/issues/test_152_resource_mime_type.py | Updates mime-type test to explicit TextResourceContents/BlobResourceContents results. |
| tests/issues/test_129_resource_templates.py | Updates resource-template listing test to use Client(mcp) rather than internal handler access. |
| tests/experimental/tasks/test_spec_compliance.py | Migrates to experimental.enable_tasks(on_*=...) and skips partial-capability tests for now. |
| tests/experimental/tasks/test_elicitation_scenarios.py | Migrates scenario servers to on_* handlers and uses ctx.session APIs. |
| tests/experimental/tasks/server/test_integration.py | Refactors integration tests to use Client(server) and lifespan context patterns. |
| tests/client/transports/test_memory.py | Updates in-memory transport test server to on_list_resources and ListResourcesResult. |
| tests/client/test_output_schema_validation.py | Refactors malicious-server simulation to just return invalid structured content from low-level server. |
| tests/client/test_list_methods_cursor.py | Updates low-level server cursor echo test to (ctx, params) list-tools handler. |
| tests/client/test_http_unicode.py | Migrates unicode server example in tests to on_* handlers and typed params/results. |
| tests/client/test_client.py | Updates client fixture server to provide required capabilities via on_* handlers returning EmptyResult/CompleteResult. |
| src/mcp/shared/message.py | Types request_context as Any to keep transport-agnostic metadata while allowing None. |
| src/mcp/shared/experimental/tasks/helpers.py | Updates docs/example snippets to new (ctx, params) task handlers. |
| src/mcp/shared/_context.py | Makes request_id optional so one context type can support both requests and notifications. |
| src/mcp/server/streamable_http_manager.py | Updates Server generic usage after type parameter simplification. |
| src/mcp/server/session.py | Updates module docs/example to the new handler API and explicit result types. |
| src/mcp/server/mcpserver/server.py | Wires core MCP protocol handlers via low-level Server(..., on_*=...) and updates context access. |
| src/mcp/server/lowlevel/func_inspection.py | Removes obsolete decorator-era call wrapper utility. |
| src/mcp/server/lowlevel/experimental.py | Replaces decorator registration with enable_tasks(on_*=...) and method-string handler wiring. |
| src/mcp/server/lowlevel/init.py | Minor __all__ ordering update. |
| src/mcp/server/experimental/task_result_handler.py | Updates usage docs to reflect enable_tasks(on_task_result=...). |
| src/mcp/server/experimental/request_context.py | Updates example snippet to the new handler signature. |
| src/mcp/server/context.py | Sets default typevar for lifespan context and keeps request type default as Any. |
| src/mcp/server/init.py | Exports ServerRequestContext at mcp.server top-level. |
| examples/snippets/servers/pagination_example.py | Migrates snippet to on_list_resources and (ctx, params) pagination. |
| examples/snippets/servers/lowlevel/structured_output.py | Migrates snippet to explicit CallToolResult (content + structured_content) and create_initialization_options(). |
| examples/snippets/servers/lowlevel/lifespan.py | Migrates snippet to on_* handlers and typed lifespan context. |
| examples/snippets/servers/lowlevel/direct_call_tool_result.py | Migrates snippet to on_* handlers returning CallToolResult directly. |
| examples/snippets/servers/lowlevel/basic.py | Migrates snippet to on_list_prompts/on_get_prompt and explicit result types. |
| examples/servers/structured-output-lowlevel/mcp_structured_output_lowlevel/main.py | Migrates example to explicit CallToolResult and create_initialization_options(). |
| examples/servers/sse-polling-demo/mcp_sse_polling_demo/server.py | Migrates example handlers to on_* and uses ctx.session for logs/stream control. |
| examples/servers/simple-tool/mcp_simple_tool/server.py | Migrates to on_list_tools/on_call_tool and returns CallToolResult. |
| examples/servers/simple-task/mcp_simple_task/server.py | Migrates to on_* handlers; task support enabled via experimental.enable_tasks(). |
| examples/servers/simple-task-interactive/mcp_simple_task_interactive/server.py | Migrates to on_* handlers and passes ctx through to task helpers. |
| examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py | Migrates to on_* handlers and uses StreamableHTTPSessionManager with new server type. |
| examples/servers/simple-streamablehttp-stateless/mcp_simple_streamablehttp_stateless/server.py | Migrates stateless streamable-http example to on_* handlers. |
| examples/servers/simple-resource/mcp_simple_resource/server.py | Migrates resources example to on_* and explicit ReadResourceResult. |
| examples/servers/simple-prompt/mcp_simple_prompt/server.py | Migrates prompts example to on_* and typed params/results. |
| examples/servers/simple-pagination/mcp_simple_pagination/server.py | Refactors pagination example to shared helper + on_* handlers for all list/read/call methods. |
| examples/servers/everything-server/mcp_everything_server/server.py | Updates low-level handler registration via _add_request_handler and new request param types. |
| docs/migration.md | Adds migration guidance for on_* handlers, kw-only constructor params, and removed auto-wrapping. |
| docs/experimental/index.md | Updates tasks docs to emphasize experimental.enable_tasks() rather than decorators. |
| README.v2.md | Updates embedded snippets to match new low-level handler API and init options helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py
Outdated
Show resolved
Hide resolved
…g changes - Document Server type parameter reduction (Generic[L, R] -> Generic[L]) - Document request_handlers/notification_handlers removal - Document subscribe capability bug fix - Fix RequestContext -> ServerRequestContext in prose - Clarify request_ctx as internal implementation detail - Remove task_store from enable_tasks examples to focus on handler changes - Fix Request import to Any in type parameter example - Add missing typing import
Merge upstream/main into fix/1671-serversession-progress-callback. Resolved import conflict in mcpserver/server.py by keeping both the new ProgressFnT import and the expanded mcp.types imports from the lowlevel Server decorator refactor (modelcontextprotocol#1985).
|
Just for tracking, here is the testing scripts Claude wrote, as well as steps for running each of the examples one by one. Script to run all examples files```bash #!/bin/bash # Record asciinema demos showing real shell commands users can copy/paste. # Each recording shows: shell prompt → command → output set -e cd /fake/path/python-sdk mkdir -p evidenceHelper: simulate typing a command at a shell prompt, then run itrun_cmd() { ── 1. simple-tool ──────────────────────────────────────────────────────────echo "Recording 01-simple-tool..." echo "━━━ simple-tool (stdio) ━━━" run_cmd "cd examples/servers/simple-tool" cat << "SCRIPT" $ uv run python -c " async def main(): uv run python -c " async def main(): ── 2. simple-prompt ────────────────────────────────────────────────────────echo "Recording 02-simple-prompt..." echo -e "\033[1;32m$\033[0m cd examples/servers/simple-prompt" cat << "SCRIPT" $ uv run python -c "...client script..." uv run python -c " async def main(): ── 3. simple-resource ──────────────────────────────────────────────────────echo "Recording 03-simple-resource..." async def main(): ── 4. simple-pagination ────────────────────────────────────────────────────echo "Recording 04-simple-pagination..." async def main(): ── 5-8. Snippets ───────────────────────────────────────────────────────────echo "Recording 05-snippet-basic..." async def main(): echo "Recording 06-snippet-direct-call-tool..." async def main(): echo "Recording 07-snippet-lifespan..." async def main(): echo "Recording 08-snippet-structured-output..." async def main(): ── 9. structured-output-lowlevel ───────────────────────────────────────────echo "Recording 09-structured-output-lowlevel..." async def main(): ── 10-15. HTTP servers ──────────────────────────────────────────────────────record_http_demo() { echo \"━━━ $title ━━━\" cd $server_dir for i in \$(seq 1 30); do echo \" Server is running on http://127.0.0.1:$port\\\" uv run --frozen python -c \"$client_code\" echo \"\" kill \$SERVER_PID 2>/dev/null; wait \$SERVER_PID 2>/dev/null record_http_demo "10" "simple-streamablehttp" async def main(): anyio.run(main) record_http_demo "11" "simple-streamablehttp-stateless" async def main(): anyio.run(main) record_http_demo "12" "everything-server" async def main(): anyio.run(main) record_http_demo "13" "simple-task" async def main(): anyio.run(main) record_http_demo "14" "simple-task-interactive" async def main(): anyio.run(main) record_http_demo "15" "sse-polling-demo" async def main(): anyio.run(main) echo "" Expected output: 2. simple-prompt (stdio)What it does: Provides a cd examples/servers/simple-prompt
uv run python -c "
import anyio
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
async def main():
params = StdioServerParameters(command='uv', args=['run', 'mcp-simple-prompt', '--transport', 'stdio'])
async with stdio_client(params) as streams:
async with ClientSession(*streams) as session:
await session.initialize()
prompts = await session.list_prompts()
for p in prompts.prompts:
print(f'Prompt: {p.name} - {p.description}')
result = await session.get_prompt('simple', {'context': 'hello', 'topic': 'MCP'})
for msg in result.messages:
print(f' [{msg.role}]: {msg.content.text}')
anyio.run(main)
"Expected output: 3. simple-resource (stdio)What it does: Provides 3 text resources: greeting, help, about. cd examples/servers/simple-resource
uv run python -c "
import anyio
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
async def main():
params = StdioServerParameters(command='uv', args=['run', 'mcp-simple-resource', '--transport', 'stdio'])
async with stdio_client(params) as streams:
async with ClientSession(*streams) as session:
await session.initialize()
resources = await session.list_resources()
for r in resources.resources:
result = await session.read_resource(r.uri)
print(f'{r.name}: {result.contents[0].text}')
anyio.run(main)
"Expected output: 4. simple-pagination (stdio)What it does: Demonstrates pagination with 25 tools (5/page), 30 resources (10/page), 20 prompts (7/page). cd examples/servers/simple-pagination
uv run python -c "
import anyio
from mcp import types
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
async def main():
params = StdioServerParameters(command='uv', args=['run', 'mcp-simple-pagination', '--transport', 'stdio'])
async with stdio_client(params) as streams:
async with ClientSession(*streams) as session:
await session.initialize()
all_tools, cursor, page = [], None, 0
while True:
p = types.PaginatedRequestParams(cursor=cursor) if cursor else None
result = await session.list_tools(params=p)
all_tools.extend(result.tools)
page += 1
print(f'Page {page}: {len(result.tools)} tools')
if not result.next_cursor: break
cursor = result.next_cursor
print(f'Total: {len(all_tools)} tools across {page} pages')
anyio.run(main)
"Expected output: 5. snippet: lowlevel/basic.py (stdio)What it does: Basic low-level server with prompts using the kwargs constructor pattern. uv run --frozen python -c "
import anyio
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
async def main():
params = StdioServerParameters(
command='uv', args=['run', '--frozen', 'python', 'examples/snippets/servers/lowlevel/basic.py'])
async with stdio_client(params) as streams:
async with ClientSession(*streams) as session:
await session.initialize()
prompts = await session.list_prompts()
print(f'Prompts: {[p.name for p in prompts.prompts]}')
result = await session.get_prompt('example-prompt', {'arg1': 'test'})
print(f'Message: {result.messages[0].content.text}')
anyio.run(main)
"Expected output: 6. snippet: lowlevel/direct_call_tool_result.py (stdio)What it does: Tool returning uv run --frozen python -c "
import anyio
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
async def main():
params = StdioServerParameters(
command='uv', args=['run', '--frozen', 'python', 'examples/snippets/servers/lowlevel/direct_call_tool_result.py'])
async with stdio_client(params) as streams:
async with ClientSession(*streams) as session:
await session.initialize()
result = await session.call_tool('advanced_tool', {'message': 'hello'})
print(f'Text: {result.content[0].text}')
print(f'Structured: {result.structured_content}')
anyio.run(main)
"Expected output: 7. snippet: lowlevel/lifespan.py (stdio)What it does: Server with lifespan management — connects a mock database on startup, disconnects on shutdown. uv run --frozen python -c "
import anyio
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
async def main():
params = StdioServerParameters(
command='uv', args=['run', '--frozen', 'python', 'examples/snippets/servers/lowlevel/lifespan.py'])
async with stdio_client(params) as streams:
async with ClientSession(*streams) as session:
await session.initialize()
result = await session.call_tool('query_db', {'query': 'SELECT * FROM users'})
print(f'Result: {result.content[0].text}')
anyio.run(main)
"Expected output: 8. snippet: lowlevel/structured_output.py (stdio)What it does: Tool with uv run --frozen python -c "
import anyio
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
async def main():
params = StdioServerParameters(
command='uv', args=['run', '--frozen', 'python', 'examples/snippets/servers/lowlevel/structured_output.py'])
async with stdio_client(params) as streams:
async with ClientSession(*streams) as session:
await session.initialize()
tools = await session.list_tools()
print(f'Has output_schema: {tools.tools[0].output_schema is not None}')
result = await session.call_tool('get_weather', {'city': 'San Francisco'})
print(f'Structured: {result.structured_content}')
anyio.run(main)
"Expected output: 9. structured-output-lowlevel (stdio)What it does: Full example server with structured output (simulated weather data). uv run --frozen python -c "
import anyio
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
async def main():
params = StdioServerParameters(
command='uv',
args=['run', '--frozen', 'python',
'examples/servers/structured-output-lowlevel/mcp_structured_output_lowlevel/__main__.py'])
async with stdio_client(params) as streams:
async with ClientSession(*streams) as session:
await session.initialize()
result = await session.call_tool('get_weather', {'city': 'Tokyo'})
print(f'Structured keys: {list(result.structured_content.keys())}')
anyio.run(main)
"Expected output: 10. simple-streamablehttp (HTTP)What it does: Streamable HTTP server with notification stream tool and event store for resumability. Terminal 1 — Start the server: cd examples/servers/simple-streamablehttp
uv run mcp-simple-streamablehttp --port 3000Wait until you see: Terminal 2 — Connect a client (from repo root): uv run --frozen python -c "
import anyio
from mcp.client.client import Client
async def main():
async with Client('http://127.0.0.1:3000/mcp') as client:
tools = await client.list_tools()
print(f'Tools: {[t.name for t in tools.tools]}')
result = await client.call_tool('start-notification-stream', {'interval': 0.5, 'count': 3, 'caller': 'test'})
print(f'Result: {result.content[0].text}')
anyio.run(main)
"Expected output: 11. simple-streamablehttp-stateless (HTTP)What it does: Stateless streamable HTTP server (no session persistence between requests). Terminal 1 — Start the server: cd examples/servers/simple-streamablehttp-stateless
uv run mcp-simple-streamablehttp-stateless --port 3000Terminal 2 — Connect a client (from repo root): uv run --frozen python -c "
import anyio
from mcp.client.client import Client
async def main():
async with Client('http://127.0.0.1:3000/mcp') as client:
tools = await client.list_tools()
print(f'Tools: {[t.name for t in tools.tools]}')
result = await client.call_tool('start-notification-stream', {'interval': 0.5, 'count': 3, 'caller': 'test'})
print(f'Result: {result.content[0].text}')
anyio.run(main)
"Expected output: Same as #10. 12. everything-server (HTTP)What it does: Conformance test server implementing all MCP features — 13 tools, resources, prompts, completions, subscriptions, sampling, elicitation. Terminal 1 — Start the server: cd examples/servers/everything-server
uv run mcp-everything-server --port 3001Terminal 2 — Connect a client (from repo root): uv run --frozen python -c "
import anyio
from mcp.client.client import Client
async def main():
async with Client('http://127.0.0.1:3001/mcp') as client:
tools = await client.list_tools()
print(f'Tools ({len(tools.tools)}): {[t.name for t in tools.tools]}')
result = await client.call_tool('test_simple_text', {})
print(f'test_simple_text: {result.content[0].text}')
resources = await client.list_resources()
print(f'Resources: {[str(r.uri) for r in resources.resources]}')
prompts = await client.list_prompts()
print(f'Prompts: {[p.name for p in prompts.prompts]}')
anyio.run(main)
"Expected output: 13. simple-task (HTTP)What it does: Demonstrates MCP tasks with a long-running tool that requires task-augmented invocation. Terminal 1 — Start the server: cd examples/servers/simple-task
uv run mcp-simple-task --port 8000Terminal 2 — Connect a client (from repo root): uv run --frozen python -c "
import anyio
from mcp.client.client import Client
async def main():
async with Client('http://127.0.0.1:8000/mcp') as client:
tools = await client.list_tools()
t = tools.tools[0]
print(f'Tool: {t.name}')
print(f' task_support: {t.execution.task_support}')
anyio.run(main)
"Expected output: 14. simple-task-interactive (HTTP)What it does: Interactive task server demonstrating elicitation and sampling via tasks. Terminal 1 — Start the server: cd examples/servers/simple-task-interactive
uv run mcp-simple-task-interactive --port 8000Terminal 2 — Connect a client (from repo root): uv run --frozen python -c "
import anyio
from mcp.client.client import Client
async def main():
async with Client('http://127.0.0.1:8000/mcp') as client:
tools = await client.list_tools()
for t in tools.tools:
print(f'Tool: {t.name} (task_support={t.execution.task_support})')
anyio.run(main)
"Expected output: 15. sse-polling-demo (HTTP)What it does: Demonstrates SSE polling pattern with periodic stream close and client auto-reconnect. Terminal 1 — Start the server: cd examples/servers/sse-polling-demo
uv run mcp-sse-polling-demo --port 3000Terminal 2 — Connect a client (from repo root): uv run --frozen python -c "
import anyio
from mcp.client.client import Client
async def main():
async with Client('http://127.0.0.1:3000/mcp') as client:
tools = await client.list_tools()
print(f'Tools: {[t.name for t in tools.tools]}')
result = await client.call_tool('process_batch', {'items': 3, 'checkpoint_every': 10})
print(f'Result: {result.content[0].text}')
anyio.run(main)
"Expected output: Here are the GIFs for Claude running each of these examples: Open to see GIFs1. simple-tool (stdio)2. simple-prompt (stdio)3. simple-resource (stdio)4. simple-pagination (stdio)5. snippet: basic.py (stdio)6. snippet: direct_call_tool_result.py (stdio)7. snippet: lifespan.py (stdio)8. snippet: structured_output.py (stdio)9. structured-output-lowlevel (stdio)10. simple-streamablehttp (HTTP)11. simple-streamablehttp-stateless (HTTP)12. everything-server (HTTP)13. simple-task (HTTP)14. simple-task-interactive (HTTP)15. sse-polling-demo (HTTP) |
















Replace the decorator-based handler registration on the lowlevel
Serverwith directon_*keyword arguments on the constructor. Handlers are now raw callables with a uniform(ctx, params) -> resultsignature, dispatched by method string.Supersedes #1968.